﻿// Learn more about F# at http://fsharp.net
// See the 'F# Tutorial' project for more help.
open System.Windows.Media.Imaging
open System.Windows.Media
open System.IO.Packaging
open System.IO

exception BadFileNameException of string

type CommandLineOptions = {
    importFilePath: string;
    exportFilePath: string;
    quality: float32
    frameRate: int
    format: PixelFormat
    }

let printHelp() =
    printfn "usage:\tajxrencoder.exe -i input_#.png -o output.ajxr -r frameRate -q quality [-f format]"
    printfn "-i:\tThe first frame of the PNG sequence to be encoder. File name must be in the form of filename_framenumber.png."
    printfn "-o:\tThe path at which to output the generated animated JPEG XR file."
    printfn "-r:\tThe frame rate at which the animated JPEG XR file should be encoded."
    printfn "-q:\tThe quality at which to encode the animated JPEG XR file. Must be a values value between 0 and 1.0. 1.0 quality means lossless."
    printfn "-f:\tAn optional pixel format with which to encode the animated JPEG XR file. If no format is provided then the format of the source PNGs will be used. Possible values for this parameter are as follows:"
    printfn "\tBgr101010" 
    printfn "\tBgr24" 
    printfn "\tBgr32" 
    printfn "\tBgr555"
    printfn "\tBgr565"
    printfn "\tBgra32" 
    printfn "\tBlackWhite"
    printfn "\tCmyk32" 
    printfn "\tGray16" 
    printfn "\tGray8" 
    printfn "\tGray32Float" 
    printfn "\tRgb128Float" 
    printfn "\tRgb24" 
    printfn "\tRgb48"
    printfn "\tRgba128Float"
    printfn "\tRgba64" 
    printfn "example:\tajxrencoder.exe -i input_0.png -o output.ajxr -r 24 -q 0.8 -f Rgb24" 

let getPixelFormat str =
    match str with
        | "Bgr101010" -> PixelFormats.Bgr101010
        | "Bgr24" -> PixelFormats.Bgr24
        | "Bgr32" -> PixelFormats.Bgr32
        | "Bgr555" -> PixelFormats.Bgr555
        | "Bgr565" -> PixelFormats.Bgr565
        | "Bgra32" -> PixelFormats.Bgra32
        | "BlackWhite" -> PixelFormats.BlackWhite
        | "Cmyk32" -> PixelFormats.Cmyk32
        | "Gray16" -> PixelFormats.Gray16
        | "Gray8" -> PixelFormats.Gray8
        | "Gray32Float" -> PixelFormats.Gray32Float
        | "Rgb128Float" -> PixelFormats.Rgb128Float
        | "Rgb24" -> PixelFormats.Rgb24
        | "Rgb48" -> PixelFormats.Rgb48
        | "Rgba128Float" -> PixelFormats.Rgba128Float
        | "Rgba64" -> PixelFormats.Rgba64
        | _ -> raise (System.Exception("Invalid pixel format"))

let rec parseCommands args optionsSoFar =
    match args with
    | [] -> optionsSoFar
    | "-i"::xs -> 
        if(not (File.Exists(xs.Head))) then
             raise (FileNotFoundException("Input file path not a valid directory."))
        let newOptionsSoFar = { optionsSoFar with importFilePath = xs.Head}
        parseCommands xs.Tail newOptionsSoFar
    | "-o"::xs -> 
        if(not (Directory.Exists(Path.GetDirectoryName(xs.Head)))) then
             raise (DirectoryNotFoundException("Export path not a valid directory."))
        let newOptionsSoFar = { optionsSoFar with exportFilePath = xs.Head}
        parseCommands xs.Tail newOptionsSoFar
    | "-r"::xs -> 
        let newOptionsSoFar = { optionsSoFar with frameRate = System.Int32.Parse(xs.Head)}
        parseCommands xs.Tail newOptionsSoFar
    | "-q"::xs -> 
        let newOptionsSoFar = { optionsSoFar with quality = System.Single.Parse(xs.Head)}
        parseCommands xs.Tail newOptionsSoFar
    | "-f"::xs -> 
        let newOptionsSoFar = { optionsSoFar with format = getPixelFormat xs.Head}
        parseCommands xs.Tail newOptionsSoFar
    | x::xs ->
        raise (System.ArgumentException("Invalid arguments provided"))


let parseInputFileName (fileName:string) =
    if(not (fileName.Contains("_")) || not (fileName.EndsWith(".png"))) then
        raise (BadFileNameException("Input file name is not in the correct format."))

    let startOfNum = fileName.LastIndexOf('_') + 1
    let endOfNum = fileName.LastIndexOf(".png")
    let num = System.Int32.Parse(fileName.Substring(startOfNum, endOfNum - startOfNum))
    (fileName.Substring(0, startOfNum), fileName.Substring(endOfNum, fileName.Length - endOfNum), num)


[<EntryPoint>]
let main argv = 
    try
        let defaultOptions = { importFilePath = ""; exportFilePath = ""; frameRate = 0; quality = float32 1.0; format = PixelFormats.Default }

        let options = parseCommands (Array.toList argv) defaultOptions
        
//        ["-i"; "C:\\Users\\Brian\\Desktop\\gf_test\\gf_hq\\gf_hq_0.png"; 
//                                     "-o"; "C:\\Users\\Brian\\Desktop\\gf_test\\out\\gf_hq_bw.ajxr"; 
//                                     "-q"; "0.5"; 
//                                     "-f"; PixelFormats.Rgb128Float.ToString() + "_"]

        if(options.importFilePath = "" || options.exportFilePath = "" || options.frameRate < 1) then
            raise (System.ArgumentException("Invalid arguments provided"))


        let startOfFilePath, endOfFilePath, currentFrame = parseInputFileName options.importFilePath

        //file names must be in the format fileName_#.png
        
        use package = System.IO.Packaging.Package.Open(options.exportFilePath, FileMode.Create)
        
        let packagePartDocument = package.CreatePart(new  System.Uri("/settings.xml", System.UriKind.Relative), System.Net.Mime.MediaTypeNames.Text.Xml, System.IO.Packaging.CompressionOption.NotCompressed)
                    
        let xmlDoc = new System.Xml.XmlDocument()
        xmlDoc.AppendChild(xmlDoc.CreateElement("Settings")) |> ignore
        xmlDoc.DocumentElement.Attributes.Append(xmlDoc.CreateAttribute("animated")).Value <- true.ToString()
        xmlDoc.DocumentElement.Attributes.Append(xmlDoc.CreateAttribute("frameRate")).Value <- options.frameRate.ToString()
        xmlDoc.Save(packagePartDocument.GetStream())


        let rec encodeSequence currentFrame (package:Package)=        
            match File.Exists(startOfFilePath + string currentFrame + endOfFilePath) with
                | false -> ()
                | true ->  
                    use inFileStream = new FileStream(startOfFilePath + string currentFrame + endOfFilePath, FileMode.Open)
                    let pngDecoder = new PngBitmapDecoder(inFileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad)
                    
                    //use outFileStream = new FileStream(options.exportFilePath + string currentFrame + ".jxr", FileMode.Create)
                    
                    let packagePartDocument = package.CreatePart(new  System.Uri("/" + string currentFrame + ".jxr", System.UriKind.Relative), "image/vnd.ms-photo", System.IO.Packaging.CompressionOption.NotCompressed)
                    
                    //let t = package.CreateRelationship(packagePartDocument.Uri, TargetMode.Internal, null)
                    
                    
                    let jpegxrEncoder = new WmpBitmapEncoder()
                    let t = new FormatConvertedBitmap(pngDecoder.Frames.[0], options.format, null,1.0)
                    jpegxrEncoder.Frames.Add(BitmapFrame.Create(t));
                    jpegxrEncoder.ImageQualityLevel <- options.quality
                    jpegxrEncoder.Save(packagePartDocument.GetStream())
                    printfn "Frame number %A converted" currentFrame
                    encodeSequence (currentFrame + 1) package

        encodeSequence currentFrame package
        printfn "Animated JPEG XR file successfully saved to %s" options.exportFilePath
    with
        | :? System.Exception as e -> 
            printfn "Error: %s" e.Message
            printHelp()
    0 // return an integer exit code
